package main

import (
	"fmt"
	"math/rand"
	"sort"
	"time"
)

/** 数组 切片: var name = [len]Type{ele, ele, ele...} */
// 数组是值类型，每次对数组元素进行修改时：将原值复制一份然后修改

func test(slice []int) {
	slice[0] = 100
}

func fbnq(n int) []uint64 {
	if n <= 0 {
		return []uint64{}
	}
	fbnqSlice := make([]uint64, n)
	fbnqSlice[0], fbnqSlice[1] = 1, 1
	for i := 2; i < len(fbnqSlice); i++ {
		fbnqSlice[i] = fbnqSlice[i-1] + fbnqSlice[i-2]
	}
	return fbnqSlice
}

func main() {
	// 0. 数组内存分配
	// 数组各个元素地址取决于 数组的类型决定，比如int32则间隔4个字节、int类型则间隔8个字节 int16=2字节...
	var intArr [3]int
	fmt.Printf("数组的内存地址为%p\n", &intArr)         // 0xc0000b6000
	fmt.Printf("数组的第一个元素内存地址为%p\n", &intArr[0]) // 0xc0000b6000
	fmt.Printf("数组的第二个元素内存地址为%p\n", &intArr[1]) // 0xc0000b6008
	fmt.Printf("数组的第三个元素内存地址为%p\n", &intArr[2]) // 0xc0000b6010

	// 1. 数组基础定义
	var a = [3]int{1, 2, 3}
	var b = [3]string{"1", "2", "3"}
	var c = [...]bool{true, false, true, false, true}

	fmt.Println(a)    // [1 2 3]
	fmt.Println(b)    // [1 2 3]
	fmt.Println(c)    // [true false true false true]
	fmt.Println(c[0]) // true

	// 2. 使用索引方式初始化
	d := [...]string{1: "GoLang", 3: "Python", 7: "Java"}
	fmt.Println(d) // [ GoLang  Python    Java]

	// 3. 数组遍历
	// 3.1 方式一
	var eList = [...]string{"北京", "上海", "深圳"}
	for i := 0; i < 3; i++ {
		fmt.Println(eList[i])
	}
	// 3.2 方式二
	i := 0
	for i < 3 {
		fmt.Println(eList[i])
		i++
	}
	// 3.3 方式三
	for _, v := range eList {
		fmt.Println(v)
	}

	var nums = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	for key, value := range nums {
		// value只是一个临时变量，不断的被重新赋值，修改它并不会影响原数组
		value += 1
		fmt.Printf("key: %d, value: %d, nums: %d\n", key, value, nums[key])
	}

	// 4. 二维数组
	f := [3][2]string{
		{"北京", "上海"},
		{"广州", "深圳"},
		{"成都", "重庆"},
	}

	fmt.Println(a)       //[[北京 上海] [广州 深圳] [成都 重庆]]
	fmt.Println(f[2][1]) //支持索引取值:重庆

	// 5, 多维数组遍历
	g := [3][2]string{
		{"北京", "上海"},
		{"广州", "深圳"},
		{"成都", "重庆"},
	}

	for _, v1 := range g {
		for _, v2 := range v1 {
			fmt.Printf("%s\t", v2)
		}
		fmt.Println()
	}

	// 6.1 从[1, 3, 5, 7, 8]中找到合为8的两个元素的下标分别为(0,3)(1,2)
	var oneLst = [...]int{1, 3, 5, 7, 8}
	for i := 0; i < len(oneLst); i++ {
		for j := i + 1; j < len(oneLst); j++ {
			if oneLst[i]+oneLst[j] == 8 {
				fmt.Printf("(%v,%v)\n", i, j)
			}
		}
	}

	var lst_arr = [3]int{1, 10, 100}
	// 6.2 求出一个数组的最大值及其下标
	maxVal := lst_arr[0]
	maxIndex := 0
	for i := 1; i < len(lst_arr); i++ {
		//从第二个 元素开始循环比较，如果发现有更大的，则交换
		if maxVal < lst_arr[i] {
			maxVal = lst_arr[i]
			maxIndex = i
		}
	}
	fmt.Printf("maxVal=%v, maxIndex=%v\n", maxVal, maxIndex) // maxVal=100, maxIndex=2
	// 6.3 求出一个数组的和以及平均值
	_sum := 0
	for _, v := range lst_arr {
		_sum += v
	}
	fmt.Println("数组总和为:", _sum, ", 平均值为:", _sum/len(lst_arr)) // 数组总和为: 111 , 平均值为: 37

	var paiXuLst = []int{8, 21, 13, 40, 5, 10, 12, 32}
	var paiXuLength = len(paiXuLst)

	// 7. 选择排序
	for i := 0; i < paiXuLength; i++ {
		for j := 0; j < paiXuLength-1-i; j++ {
			if paiXuLst[j] > paiXuLst[j+1] {
				paiXuLst[j], paiXuLst[j+1] = paiXuLst[j+1], paiXuLst[j]
			}
		}
	}
	fmt.Println("选择排序:", paiXuLst) // 选择排序: [5 8 10 12 13 21 32 40]

	// 8. 冒泡排序
	for i := 0; i < paiXuLength; i++ {
		for j := 0; j < paiXuLength; j++ {
			if paiXuLst[i] < paiXuLst[j] {
				paiXuLst[i], paiXuLst[j] = paiXuLst[j], paiXuLst[i]
			}
		}
	}
	fmt.Println("冒泡排序:", paiXuLst) // 冒泡排序: [5 8 10 12 13 21 32 40]

	// 9. sort排序
	sort.Ints(paiXuLst)
	fmt.Println("sort:", paiXuLst) // sort: [5 8 10 12 13 21 32 40]

	// 10. 随机生成五个数并反转输出
	var intArr2 [5]int
	rand.Seed(time.Now().UnixNano()) // 种子
	for i := 0; i < len(intArr2); i++ {
		intArr2[i] = rand.Intn(100)
	}
	fmt.Println("原数组:", intArr2)

	temp := 0
	for i := 0; i < len(intArr2)/2; i++ {
		temp = intArr2[len(intArr2)-1-i]
		intArr2[len(intArr2)-1-i] = intArr2[i]
		intArr2[i] = temp
	}
	fmt.Println("倒叙数组:", intArr2)

	/**
	a.切片(slice)是数组的一个引用, slice为引用类型
	b.切片的长度是可以变化的，因此切片是一个动态的数组
	c.切片的基本语法：var 变量名 = []类型，例如 var a = []int{...}
	d.切片的范围同所有的编程语言一样，都为·左闭右开·

	与数组定义对比
	var a = [3]int{1, 2, 3}
	var b = [3]string{"1", "2", "3"}
	var c = [...]bool{true, false, true, false, true}
	*/
	// 切片方式一：使用步长
	// 11.1 切片
	var abc []int = []int{1, 2, 3, 4, 5}
	slice := abc[1:3]
	fmt.Printf("切片后值、长度及容量为:%v %v %v\n", slice, len(slice), cap(slice)) // [2 3] 2 4
	// new_slice := append(slice, 6) // 追加一个元素
	// new_slice := append(slice, 6, 7, 8) // 追加多个元素
	new_slice := append(slice, []int{6, 7, 8, 9}...)                                // 追加一个新的切片
	fmt.Printf("切片追加后值、长度及容量%v %v %v\n", new_slice, len(new_slice), cap(new_slice)) // [2 3 6 7 8 9] 6 8

	// 切片方式二：var []切片名 []type = make([]type, len, cap) 或 var 切片名 = make([], len, cap); cap可选
	// 新创建的切片的数组元素默认都为0
	// 11.2 切片赋值以及拷贝
	// 赋值拷贝，会将原来slice的地址拷贝，新旧slice共享内存
	// copy(new, old) 函数copy只会将slice内容进行拷贝
	var strSlice = make([]string, 5, 8)
	fmt.Println("strSlice:", strSlice) // strSlice: [    ]
	var intSlice []int = make([]int, 5, 8)
	fmt.Println("intSlice:", intSlice) // intSlice: [0 0 0 0 0]
	var def = make([]int, 1)
	fmt.Println("def:", def) // [0]
	copy(def, abc)
	fmt.Println("def:", def) // [1]

	// 切片方式三：定义切片时，直接指定具体数组
	// var ownerSlice [3]int = [3]int{1, 3, 5}
	var ownerSlice []int = []int{1, 3, 5}
	fmt.Println("ownerSlice:", ownerSlice, cap(ownerSlice)) // ownerSlice: [1 3 5] 3

	// 切片的遍历
	index := 0
	for index < len(ownerSlice) {
		fmt.Println("ownerSlice value:", ownerSlice[index]) // 1 3 5
		index++
	}

	for _, v := range ownerSlice {
		fmt.Println("ownerSlice value:", v) // 1 3 5
	}

	// 12. 当切片作为入参时，为: `引用传递`
	// 引用传递作为参数传递时候传入的为切片的地址，则修改切片元素值，外部的值会同步修改
	var ghj = []int{1, 2, 3, 4, 5}
	fmt.Println("ghj:", ghj) // [1 2 3 4 5]
	test(ghj)
	fmt.Println("ghj:", ghj) // [100 2 3 4 5]

	// 13. 切片练习：编写一个斐波那契，接收一个int，将斐波那契的数列放在切片中
	res := fbnq(10)
	fmt.Println("res:", res) // res: [1 1 2 3 5 8 13 21 34 55]
}
